/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.content;

import com.blandware.atleap.common.Constants;
import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.StringUtil;
import com.blandware.atleap.model.core.ContentField;
import com.blandware.atleap.model.core.ContentFieldValue;
import com.blandware.atleap.service.core.ContentFieldManager;
import com.blandware.atleap.webapp.struts.ContentTilesRequestProcessor;
import com.blandware.atleap.webapp.util.core.ApplicationResources;
import com.blandware.atleap.webapp.util.core.CacheUtil;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.taglib.TagUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.util.Locale;

/**
 * <p>Searches for content field value of content field with specified identifier
 * that belongs to {@link com.blandware.atleap.model.core.Localizable} with specified ID. Result is saved
 * to variable or written to page.
 * </p>
 * <p>
 * If nothing was found (or value is empty string), then message like "The field
 * value is empty, please insert the text" will be shown instead, if site is in
 * edit mode OR <b>ignore</b> is <code>false</code>.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>localizableId</b> - ID of localizable to which desired content field belongs
 * </li>
 * <li>
 * <b>identifier</b> - identifier of content field which value is needed
 * </li>
 * <li>
 * <b>locale</b> - identifier of content locale to use. If not given, then
 * user content locale is tried. If not found, default one is used.
 * </li>
 * <li>
 * <b>ignore</b> - if this attribute is set to <code>false</code> (or omitted)
 * and no value is found (or it's empty string), then a message like "The field
 * value is empty, please insert the text" will be treated as result. This can
 * be "true" or "false", default is "false".
 * </li>
 * <li>
 * <b>filter</b> - whether to replace characters, which are sensitive in HTML,
 * with their entity equivalents.
 * </li>
 * <li>
 * <b>var</b> - name of scope variable to store result to
 * </li>
 * <li>
 * <b>scope</b> - scope of variable
 * </li>
 * <li>
 * <b>editable</b> - whether a wrapper need to be generated that will allow
 * user to edit content field value.
 * </li>
 * </ul>
 * </p>
 * <p><a href="ContentByIdTag.java.html"><i>View Source</i></a></p>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.23 $ $Date: 2007/12/23 10:27:18 $
 * @jsp.tag name="contentById"
 * body-content="empty"
 * @see org.apache.struts.Globals#LOCALE_KEY
 */
public class ContentByIdTag extends SimpleTagSupport {

	protected transient final Log log = LogFactory.getLog(ContentByIdTag.class);
	protected static final String NUMBER_KEY = "com.blandware.atleap.taglib.content.CONTENT_BY_ID_TAG_NUMBER";

	protected ApplicationContext applicationCtx = null;

	/**
	 * ID of localizable to retrieve content field value for
	 */
	protected Long localizableId;

	/**
	 * Identifier of content field to retrieve value of
	 */
	protected String identifier;

	/**
	 * Locale to get value for. By default it is taken from session
	 */
	protected String locale;

	/**
	 * If this attribute is set to <code>false</code> (or omitted) and no value
     * is found (or it's empty string), then a message like "The field value is
     * empty, please insert the text" will be treated as result.
	 */
	protected Boolean ignore = Boolean.FALSE;

	/**
	 * Whether or not to replace characters, which are sensitive in HTML, with their entity equivalents.<br />
	 */
	protected Boolean filter = null;

	/**
	 * Name of variable to export content field value
	 */
	protected String var;

	/**
	 * Scope to export content field value to
	 */
	protected String scope;

	/**
	 * If this value is <code>Boolean.TRUE</code> content will be wrapped in &lt;span&gt;&lt;/span&gt; tags
	 * to provide access to editing content while in edit mode.
	 */
	protected Boolean editable = Boolean.TRUE;


	/**
     * Returns localizable ID
     *
	 * @return localizable ID
	 * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.Long"
	 * description="ID of localizable to retrieve content for"
	 */
	public Long getLocalizableId() {
		return localizableId;
	}

    /**
     * Sets localizable ID
     *
     * @param localizableId localizable ID to set
     */
	public void setLocalizableId(Long localizableId) {
		this.localizableId = localizableId;
	}

	/**
     * Returns identifier of field
     *
	 * @return identifier of field
	 * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Identifier of field to retrieve content of"
	 */
	public String getIdentifier() {
		return identifier;
	}

    /**
     * Sets identifier of field
     *
     * @param identifier identifier of field to set
     */
	public void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	/**
     * Returns locale
     *
	 * @return locale
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Locale to get value for"
	 */
	public String getLocale() {
		return locale;
	}

    /**
     * Sets locale
     *
     * @param locale locale to set
     */
	public void setLocale(String locale) {
		this.locale = locale;
	}

	/**
     * Returns whether to suppress output of message like "The field value is
     * empty, please insert the text" for empty or absent CFV while not in edit
     * mode
     *
	 * @return whether to suppress 'empty CFV' message while not in edit mode
     * @see #ignore
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether to suppress 'empty CFV' message while not in edit mode"
	 */
	public Boolean getIgnore() {
		return ignore;
	}

    /**
     * Sets whether to suppress output of message like "The field value is
     * empty, please insert the text" for empty or absent CFV while not in edit
     * mode
     *
     * @param ignore whether to suppress 'empty CFV' message while not in edit mode
     * @see #ignore
     */
	public void setIgnore(Boolean ignore) {
		this.ignore = ignore;
	}

	/**
     * Returns whether or not to replace characters, which are sensitive in
     * HTML, with their entity equivalents.
     *
	 * @return whether or not to replace characters, which are sensitive in HTML
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not to replace characters, which are sensitive in HTML, with their entity equivalents"
	 */
	public Boolean getFilter() {
		return filter;
	}

    /**
     * Sets whether or not to replace characters, which are sensitive in HTML,
     * with their entity equivalents.
     *
     * @param filter whether or not to replace characters, which are sensitive in HTML
     */
	public void setFilter(Boolean filter) {
		this.filter = filter;
	}

	/**
     * Returns name of variable that will accept found value
     *
     * @return name of variable
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export message"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable that will accept found value
     *
     * @param var name of variable to set
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns variable scope
     *
     * @return variable scope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export variable to"
	 */
	public String getScope() {
		return scope;
	}

    /**
     * Sets variable scope
     *
     * @param scope variable scope to scope
     */
	public void setScope(String scope) {
		this.scope = scope;
	}

	/**
     * Returns whether this value is editable
     *
	 * @return whether this value is editable
     * @see #editable
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Is content editable or not"
	 */
	public Boolean getEditable() {
		return editable;
	}

    /**
     * Sets whether this value is editable
     *
     * @param editable whether this value is editable
     * @see #editable
     */
	public void setEditable(Boolean editable) {
		this.editable = editable;
	}

    /**
     * Processes the tag
     *
     * @throws JspException
     */
	public void doTag() throws JspException {
		PageContext pageContext = (PageContext) getJspContext();
		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
		ApplicationResources applicationResources = ApplicationResources.getInstance(pageContext.getServletContext());

		if ( applicationCtx == null ) {
			applicationCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(pageContext.getServletContext());
		}

		// calculate tag number
		Integer tagNumber = (Integer) pageContext.getAttribute(NUMBER_KEY, PageContext.REQUEST_SCOPE);
		if ( tagNumber == null ) {
			tagNumber = new Integer(0);
		} else {
			tagNumber = new Integer(tagNumber.intValue() + 1);
		}

		pageContext.setAttribute(NUMBER_KEY, tagNumber, PageContext.REQUEST_SCOPE);

		//get locale
		if ( locale == null ) {
			Locale l = (Locale) pageContext.getAttribute(Globals.LOCALE_KEY, PageContext.SESSION_SCOPE);
			if ( l != null ) {
				locale = l.getLanguage();
			}
		}
		if ( locale == null ) {
			locale = Locale.getDefault().getLanguage();
		}

		TagUtils tagUtils = TagUtils.getInstance();
		try {
			if ( log.isDebugEnabled() ) {
				log.debug("Processing content localizableId: " + localizableId + ", locale: " + locale + ", identifier: " + identifier);
			}

			String content = null;
			Long contentFieldId = null;
			byte contentFieldType = 0;

			CacheUtil cacheUtil = CacheUtil.getInstance(request);
			CacheUtil.CFVData cfvData;

			//try to search in cache
			cfvData = cacheUtil.getLocalizableFieldValueFromCache(localizableId, identifier, locale);

			if ( cfvData != null ) {
				content = cfvData.getData();
				contentFieldId = cfvData.getContentFieldId();
				contentFieldType = cfvData.getContentFieldType();
			}

			//not in cache, search in database
			if ( content == null ) {
				// try to search content by uri
				if ( log.isDebugEnabled() ) {
					log.debug("Trying to search in database by localizableId: " + localizableId);
				}
				ContentFieldManager contentFieldManager = (ContentFieldManager) applicationCtx.getBean(Constants.CONTENT_FIELD_MANAGER_BEAN);

				ContentFieldValue contentFieldValue = contentFieldManager.findContentFieldValueByLocalizableIdAndIdentifierAndLocale(localizableId, identifier, locale);
				ContentField contentField;
				if ( contentFieldValue != null ) {
					contentField = contentFieldValue.getContentField();
					if ( contentField.getType() == ContentField.LINE_TYPE ) {
						content = contentFieldValue.getSimpleValue();
					} else if ( contentField.getType() == ContentField.MULTILINE_TYPE ) {
						content = ConvertUtil.convertToString(contentFieldValue.getValue(), request.getCharacterEncoding());
					} else /* if HTML_TYPE */ {
						content = ConvertUtil.convertToString(contentFieldValue.getValue(), request.getCharacterEncoding());
					}
					contentFieldId = contentField.getId();
					contentFieldType = contentField.getType();
					//add to cache
					cfvData = new CacheUtil.CFVData(content, contentFieldId, contentFieldType, contentFieldValue.getId(), contentField.getOwner().getId());
					cacheUtil.putLocalizableFieldValueInCache(cfvData, localizableId, identifier, contentFieldValue.getContentLocale().getIdentifier());
				}
			}

			boolean encode;
			if ( filter != null ) {
				encode = filter.booleanValue();
			} else {
				if ( contentFieldType == ContentField.HTML_TYPE ) {
					encode = false;
				} else {
					encode = true;
				}
			}

			if ( encode ) {
				content = StringUtil.htmlEncode(content);
			}

			if ( var != null ) {
				if ( content != null ) {
					// save content in specified scope
					int varScope = PageContext.PAGE_SCOPE;
					if ( scope != null ) {
						varScope = tagUtils.getScope(scope);
					}
					pageContext.setAttribute(var, content, varScope);
				}
			} else {
				// write content directly to page
				Object editModeEnabledAttr = request.getSession().getAttribute(WebappConstants.SITE_EDIT_MODE_ENABLED_KEY);
				boolean editModeEnabled = editModeEnabledAttr != null && Boolean.TRUE.equals(editModeEnabledAttr);
				if ( (content == null || content.trim().length() == 0) && (editModeEnabled || !ignore.booleanValue()) ) {
					content = applicationResources.getMessage(request, "core.commons.contentTag.insertText");
				}

				if ( content != null && content.trim().length() > 0 ) {

					boolean createWrapper = editModeEnabled && editable != null && editable.booleanValue();
					if ( createWrapper ) {
						// if content field value has been found (normally it will always be) wrap content with <div></div> tags
						StringBuffer divId = new StringBuffer("__field__value__").append(contentFieldId).append("__wrapper__").append(tagNumber);
						String requestUrl = (String) request.getAttribute(ContentTilesRequestProcessor.PROCESSED_URL);

						StringBuffer onmouseover = new StringBuffer("doSelectLayer(this.id);");
						StringBuffer onmouseout = new StringBuffer("doUnselectLayer(this.id);");
						StringBuffer ondblclick = new StringBuffer("doCallFieldUpdate(").append(contentFieldId).append(", '").append(requestUrl).append("');");

						String divDisplay = "inline";
						if ( contentFieldType == ContentField.HTML_TYPE ) {
							divDisplay = "block";
						}
						if ( content.trim().length() == 0 ) {
							content = applicationResources.getMessage(request, "core.commons.contentTag.insertText");
						}
						content = new StringBuffer("<div id=\"").append(divId).append("\" name=\"").append(divId).append("\" class=\"fieldValueWrapper\" style=\"display: ").append(divDisplay).append(";\" ").append("onmouseover=\"").append(onmouseover).append("\" onmouseout=\"").append(onmouseout).append("\" ondblclick=\"").append(ondblclick).append("\">").append(content).append("</div>").toString();
					}
					tagUtils.write(pageContext, content);
				}
			}

		} catch ( Exception ex ) {
			JspTagException e = new JspTagException(ex);
			tagUtils.saveException(pageContext, e);
			throw e;
		}
	}

}
